package com.isesol.orm.jpa.query;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import com.google.common.collect.Lists;
import com.isesol.arch.common.service.ServiceException;
import com.isesol.arch.common.utils.Collections3;
import com.isesol.arch.common.utils.DateFormatType;
import com.isesol.arch.common.utils.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.data.jpa.domain.Specification;

public class DynamicSpecifications {
    public static <T> Specification<T> bySearchFilter(final Collection<SearchFilter> filters, final String groupOp) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                if (Collections3.isNotEmpty(filters)) {
                	List<Predicate> predicates = buildPredicateByFilter(filters, root, builder);

                    // 将所有条件用 and 联合起来
                    if (!predicates.isEmpty()) {
                        Predicate[] restrictions = predicates.toArray(new Predicate[predicates.size()]);
                        if (StringUtils.equals(groupOp, "OR")) {
                            return builder.or(restrictions);
                        } else {
                            return builder.and(restrictions);
                        }
                    }
                }

                return builder.conjunction();
            }
        };
    }
    
    /**
     * 使用OR连接两个查询过滤参数.
     */
    public static <T> Specification<T> bySearchFilter(final Collection<SearchFilter> filters, final Collection<SearchFilter> orFilters) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
            	Predicate firstPredicate = null;
            	Predicate secondPredicate = null;
            	
                if (Collections3.isNotEmpty(filters)) {
                    List<Predicate> predicates = buildPredicateByFilter(filters, root, builder);

                    // 将所有条件用 and 联合起来
                    if (!predicates.isEmpty()) {
                    	firstPredicate = builder.and(predicates.toArray(new Predicate[predicates.size()]));
                    }
                }
                
                if (Collections3.isNotEmpty(orFilters)) {
                	List<Predicate> predicates = buildPredicateByFilter(orFilters, root, builder);
                	
                	// 将所有条件用 and 联合起来
                    if (!predicates.isEmpty()) {
                    	secondPredicate = builder.and(predicates.toArray(new Predicate[predicates.size()]));
                    }
                }
                
                if (firstPredicate != null && secondPredicate != null) {
                	List<Predicate> predicates = Lists.newArrayList(firstPredicate, secondPredicate);
                	return builder.or(predicates.toArray(new Predicate[predicates.size()]));
                } else if (firstPredicate != null) {
                	return firstPredicate;
                } else if (secondPredicate != null) {
                	return secondPredicate;
                }

                return builder.conjunction();
            }
        };
    }
    
    /**
     * 根据查询过滤参数构造查询条件.
     * @param filters 查询过滤参数
     */
    private static List<Predicate> buildPredicateByFilter(final Collection<SearchFilter> filters, Root<?> root, CriteriaBuilder builder) {
    	if (Collections3.isNotEmpty(filters)) {
            List<Predicate> predicates = Lists.newArrayList();
            for (SearchFilter filter : filters) {
                // nested path translate, 如Task的名为"user.name"的filedName, 转换为Task.user.name属性
                String[] names = StringUtils.split(filter.fieldName, ".");
                Path expression = root.get(names[0]);
                for (int i = 1; i < names.length; i++) {
                    expression = expression.get(names[i]);
                }

                if ("id".equals(names[names.length - 1]) || expression.getJavaType().equals(Integer.class)) {
                    if (filter.value instanceof String) {
                    	if (filter.operator == SearchFilter.Operator.NN) {
                    		String[] values = ((String) filter.value).split(",");
                    		
                    		if (values != null && values.length > 0) {
                    			List<Integer> valueList = new ArrayList<Integer>(values.length);
                    			
                    			for (String value : values) {
                    				valueList.add(NumberUtils.toInt(value));
                    			}
                    			
                    			filter.value = valueList;
                    		}
                    	} else {
                    		filter.value = NumberUtils.toInt((String) filter.value);
                    	}
                    }
                } else if (expression.getJavaType().equals(Long.class)) {
                    if (filter.value instanceof String) {
                    	if (filter.operator == SearchFilter.Operator.NN) {
                    		String[] values = ((String) filter.value).split(",");
                    		
                    		if (values != null && values.length > 0) {
                    			List<Long> valueList = new ArrayList<Long>(values.length);
                    			
                    			for (String value : values) {
                    				valueList.add(NumberUtils.toLong(value));
                    			}
                    			
                    			filter.value = valueList;
                    		}
                    	} else {
                    		filter.value = NumberUtils.toLong((String) filter.value);
                    	}
                    }
                } else if (expression.getJavaType().equals(java.util.Date.class) && filter.value instanceof String) {
                    try {
                        filter.value = DateUtil.parse(filter.value.toString(), DateFormatType.FORMAT_DATETIME);
                    } catch (ParseException e) {

                        // 尝试用日期格式
                        try {
                            filter.value = DateUtil.parse(filter.value.toString(), DateFormatType.FORMAT_DATE);
                        } catch (ParseException e1) {
                            throw new ServiceException("转换java.util.Date失败: " + filter.value.toString(), e1);
                        }
                    }
                } else if (expression.getJavaType().equals(java.sql.Date.class) && filter.value instanceof String){
                    try {
                        filter.value = DateUtil.parse(filter.value.toString(), DateFormatType.FORMAT_DATE);
                    } catch (ParseException e) {
                        throw new ServiceException("转换java.sql.Date失败: " + filter.value.toString(), e);
                    }
                }

                // 类型自动纠正(数字类型不支持包含类查询)，改为EQ
                if ((expression.getJavaType().equals(Short.class)
                        || expression.getJavaType().equals(Integer.class)
                        || expression.getJavaType().equals(Long.class)
                        || expression.getJavaType().equals(Float.class)
                        || expression.getJavaType().equals(Double.class))) {
                    if (filter.operator.equals(SearchFilter.Operator.CN)
                            || filter.operator.equals(SearchFilter.Operator.NC)) {
                        filter.operator = SearchFilter.Operator.EQ;
                    }
                } else if (expression.getJavaType().equals(Boolean.class)) {
                    filter.operator = SearchFilter.Operator.EQ;
                }

                // logic operator
                switch (filter.operator) {
                    case EQ:
                        if (expression.getJavaType().equals(Boolean.class)) {
                            predicates.add(builder.equal(expression, Boolean.parseBoolean(Objects.toString(filter.value))));
                        } else {
                            predicates.add(builder.equal(expression, filter.value));
                        }
                        break;
                    case NE:
                        predicates.add(builder.notEqual(expression, filter.value));
                        break;
                    case LT:
                        predicates.add(builder.lessThan(expression, (Comparable) filter.value));
                        break;
                    case LE:
                        predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.value));
                        break;
                    case GT:
                        predicates.add(builder.greaterThan(expression, (Comparable) filter.value));
                        break;
                    case GE:
                        predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.value));
                        break;
                    case BW:
                        predicates.add(builder.like(expression, "%" + filter.value));
                        break;
                    case BN:
                        predicates.add(builder.notLike(expression, "%" + filter.value));
                        break;
                    case EW:
                        predicates.add(builder.like(expression, filter.value + "%"));
                        break;
                    case EN:
                        predicates.add(builder.notLike(expression, filter.value + "%"));
                        break;
                    case CN:
                        predicates.add(builder.like(expression, "%" + filter.value + "%"));
                        break;
                    case NC:
                        predicates.add(builder.notLike(expression, "%" + filter.value + "%"));
                        break;
                    case NU:
                        predicates.add(builder.in(expression).value(filter.value));
                        break;
                    case NN:
                        predicates.add(builder.in(expression).value(filter.value));
                        break;
                    case NULL:
                    	 predicates.add(expression.isNull());
                    	break;
                }
            }

            return predicates;
        }
    	
    	return null;
    }
}